package com.springframework.boot.awesome.annotation.arch.aspect;

import com.springframework.boot.awesome.annotation.arch.exception.ExecuteFlowFailedException;
import com.springframework.boot.awesome.annotation.arch.exception.MonitorClassNotFoundException;
import com.springframework.boot.awesome.annotation.arch.exception.MonitorClassNotImplementException;
import com.springframework.boot.awesome.annotation.arch.exception.ThreadCountLimitException;
import com.springframework.boot.awesome.annotation.flow.FlowExecute;
import com.springframework.boot.awesome.annotation.arch.runnable.FlowExecuteRunnable;
import com.springframework.boot.awesome.annotation.arch.util.ReflectUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch;


@Component
@Aspect
public class FlowExecuteAspect {
    private static int threadCountLimit = 20;

    @Pointcut("@annotation(com.springframework.boot.awesome.annotation.flow.FlowExecute)")
    public void pointcut(){

    }

    @Around("pointcut() && @annotation(flowExecute) ")
    public void around(ProceedingJoinPoint proceedingJoinPoint, FlowExecute flowExecute) throws Throwable {
        execute(flowExecute);
    }

    private void execute(FlowExecute flowExecute) throws ExecuteFlowFailedException, ClassNotFoundException,
            InterruptedException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ThreadCountLimitException, MonitorClassNotImplementException, MonitorClassNotFoundException {
        CheckParamsFlowExecute(flowExecute);
        String[] flowExecuteClassPackageArgs = flowExecute.flowExecuteClassPackageArgs();
        int[] flowExecuteThreadCountArgs = flowExecute.flowExecuteThreadCountArgs();
        String[] flowExecuteThreadNameTemplateArgs = flowExecute.flowExecuteThreadNameTemplate();

        if(flowExecuteClassPackageArgs.length == 0){
            throw new ExecuteFlowFailedException("flowExecuteClassPackageArgs length = 0");
        }
        if(flowExecuteThreadCountArgs.length == 0){
            throw new ExecuteFlowFailedException("flowExecuteThreadCountArgs length = 0");
        }
        if(flowExecuteThreadNameTemplateArgs.length == 0){
            throw new ExecuteFlowFailedException("flowExecuteThreadNameTemplateArgs length = 0");
        }

        if(flowExecuteClassPackageArgs.length != flowExecuteThreadCountArgs.length){
            throw new ExecuteFlowFailedException("flowExecuteClassPackageArgs length != flowExecuteThreadCountArgs length");
        }
        if(flowExecuteClassPackageArgs.length != flowExecuteThreadNameTemplateArgs.length){
            throw new ExecuteFlowFailedException("flowExecuteClassPackageArgs length != flowExecuteThreadNameTemplateArgs length");
        }


        for(int i = 0; i < flowExecuteClassPackageArgs.length; i ++){
            String flowExecuteClassPackage = flowExecuteClassPackageArgs[i];
            int threadCount = flowExecuteThreadCountArgs[i];
            if(threadCount > threadCountLimit || threadCount <= 0){
                throw new ThreadCountLimitException("thread count should <=20 >0");
            }
            String threadNameTemplate = flowExecuteThreadNameTemplateArgs[i];
            executeAndAwait(flowExecuteClassPackage, threadCount, threadNameTemplate);
        }
    }

    private void executeAndAwait(String classPackage, int threadCount, String threadNameTemplate) throws ClassNotFoundException, InterruptedException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, MonitorClassNotImplementException, MonitorClassNotFoundException {
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        flowExecute(classPackage, threadCount, countDownLatch, threadNameTemplate);
        countDownLatch.await();
    }

    private void flowExecute(String classPackage, int threadCount, CountDownLatch countDownLatch,
                                 String threadNameTemplate) throws ClassNotFoundException, IllegalAccessException,
            InstantiationException, InvocationTargetException, NoSuchMethodException, MonitorClassNotImplementException, MonitorClassNotFoundException {
        Object pushObj = ReflectUtil.createInstanceByPackage(classPackage);
        Class[] paramsClassArgs = new Class[]{};
        Method method = ReflectUtil.getMethod(classPackage, "execute", paramsClassArgs);
        for(int i = 1; i <= threadCount; i ++){
            FlowExecuteRunnable flowExecuteRunnable = new FlowExecuteRunnable(method, pushObj, countDownLatch);
            Thread thread = new Thread(flowExecuteRunnable);
            String threadName = String.format(threadNameTemplate + i);
            thread.setName(threadName);
            thread.start();
        }
    }

    private void CheckParamsFlowExecute(FlowExecute flowExecute) throws ExecuteFlowFailedException {
        if(flowExecute.flowExecuteClassPackageArgs() == null || flowExecute.flowExecuteClassPackageArgs().length == 0){
            throw new ExecuteFlowFailedException("FlowExecuteClassPackageArgs params is null or length = 0");
        }
    }
}
